﻿using System.Numerics;

namespace TextureGenerator.Mechanics
{
    internal class Perlin
    {
        private readonly Random r;
        private readonly int latticeSideNum;
        private readonly List<Vector2> nodes;
        public Perlin(int seed, int latticeSideNum)
        {
            r = new Random(seed);
            this.latticeSideNum = latticeSideNum;
            int vectorNum = (latticeSideNum + 1) * (latticeSideNum + 1);
            nodes = new List<Vector2>();
            do
            {
                Vector2 nv = RandVector();
                if (nv.Length() <= 1f)
                {
                    nodes.Add(nv);
                }
            }
            while (nodes.Count < vectorNum);
        }

        public float GetHeight(float xratio, float yratio)
        {
            int Lx = (int)Math.Floor(latticeSideNum * xratio);
            int Ly = (int)Math.Floor(latticeSideNum * yratio);
            Vector2 a1 = GetNode(Lx, Ly);
            Vector2 b1 = GetNode(Lx + 1, Ly);
            Vector2 c1 = GetNode(Lx, Ly + 1);
            Vector2 d1 = GetNode(Lx + 1, Ly + 1);

            float u = (float)(latticeSideNum * xratio - Lx);
            float v = (float)(latticeSideNum * yratio - Ly);
            Vector2 a2 = new(u, v);
            Vector2 b2 = new(u - 1f, v);
            Vector2 c2 = new(u, v - 1f);
            Vector2 d2 = new(u - 1f, v - 1f);

            float z1 = Dot(a1, a2);
            float z2 = Dot(b1, b2);
            float z3 = Dot(c1, c2);
            float z4 = Dot(d1, d2);

            float w0 = Ease(u);
            float w1 = Ease(v);
            float l1 = Lerp(z1, z2, w0);
            float l2 = Lerp(z3, z4, w0);
            float res = Lerp(l1, l2, w1);

            return res;
        }
        public Vector2 GetNode(int x, int y)
        {
            int index = y * (latticeSideNum + 1) + x;
            return nodes[index];
        }
        private Vector2 RandVector()
        {
            Vector2 res = new Vector2();
            res.X = r.NextSingle() * 2 - 1f;
            res.Y = r.NextSingle() * 2 - 1f;
            return res;
        }
        private static float Dot(Vector2 a, Vector2 b)
        {
            return a.X * b.X + a.Y * b.Y;
        }
        private static float Ease(float v)
        {
            return 3 * v * v - 2 * v * v * v;
        }
        private static float Lerp(float a, float b, float v)
        {
            return a + (b - a) * v;
        }
    }
}
